package ar.edu.itba.pod.Legajo47342.communication;

import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;

import ar.edu.itba.pod.Legajo47342.Node;
import ar.edu.itba.pod.Legajo47342.TPCWaitThread;
import ar.edu.itba.pod.simul.communication.Message;
import ar.edu.itba.pod.simul.communication.MessageType;
import ar.edu.itba.pod.simul.communication.ThreePhaseCommit;
import ar.edu.itba.pod.simul.communication.Transactionable;
import ar.edu.itba.pod.simul.communication.payload.Payload;

public class ThreePhaseCommitImpl implements ThreePhaseCommit {

	public enum TPCState{
		READY,
		CAN_COMMIT,
		PRE_COMMIT,
		COMMIT,
		END
	}
	
	private String coordinator;
	private Node node;
	private TPCState state;
	Thread waitThread;
	
	
	public ThreePhaseCommitImpl(Node node) throws RemoteException {
		super();
		this.node = node;
		this.coordinator = null;
		this.state = TPCState.READY;
		
		UnicastRemoteObject.exportObject(this, 0);
	}

	@Override
	public boolean canCommit(String coordinatorId, long timeout)
			throws RemoteException {
		if (state == TPCState.READY) {
			coordinator = coordinatorId;
			state = TPCState.CAN_COMMIT;
			
			//start waiting here
			waitThread = new TPCWaitThread(timeout, this);
			waitThread.setDaemon(true);
			waitThread.start();
			
			return true;
		}
		return false;
	}

	@Override
	public void preCommit(String coordinatorId) throws RemoteException {
		if (!coordinator.equals(coordinatorId)) {
			throw new IllegalArgumentException("This coordinator cannot call pre commit on this node");
		}
		if (state != TPCState.CAN_COMMIT) {
			throw new IllegalStateException("Cannot call preCommit on this state");
		}

		waitThread.interrupt();
		state = TPCState.PRE_COMMIT;
	}

	@Override
	public void doCommit(String coordinatorId) throws RemoteException {
		if (!coordinator.equals(coordinatorId)) {
			throw new IllegalArgumentException("This coordinator cannot call commit on this node");
		}
		if (state != TPCState.PRE_COMMIT) {
			throw new IllegalStateException("Cannot call doCommit on this state");
		}
		
		Transactionable trans = node.getManager().
			getConnectionManager(coordinatorId).getNodeCommunication();
		
		Payload pay = trans.getPayload();
		
		Message mes = new Message(node.getNodeId(), System.currentTimeMillis(), MessageType.RESOURCE_TRANSFER, pay);
		
		node.getManager().getGroupCommunication().send(mes, node.getNodeId());
		
		state = TPCState.READY;
	}

	@Override
	public void abort() throws RemoteException {
		//TODO agregar caso de READY con transaccion hecha
		switch (state) {
		case READY:
			throw new IllegalStateException("Cannot abort in this state");
		default:
			//en cualquiera de los otros casos vuelvo al ready. 
			//Si hice commit, ya lo hice.
			waitThread.interrupt();
			state = TPCState.READY;
			
			break;
		}

	}

	@Override
	public void onTimeout() throws RemoteException {
		
		switch (state) {
		case READY:
			throw new IllegalStateException("Cannot timeout in this state");
		default:
			System.out.println("TIMEOUT!");
			abort();
			break;
		}
		
		
	}

}
